package com.grupoconstruobras.sigo.personas.datamodel.listener;

import com.grupoconstruobras.sigo.exhumaciones.caso.datamodel.listener.QueryAbstract;
import com.grupoconstruobras.sigo.exhumaciones.ui.datamodel.IQueryBuilder;
import com.grupoconstruobras.sigo.personas.dto.PersonaConsultaDto;
import com.grupoconstruobras.sigo.general.util.SijypConstants;

import java.util.Iterator;
import java.util.List;
import java.util.StringTokenizer;

import javax.faces.context.FacesContext;
import javax.persistence.Query;

import org.apache.commons.lang3.StringUtils;
import org.richfaces.component.SortOrder;
import org.richfaces.model.SortField;

public class PersonaNaturalQuery extends QueryAbstract<Object[]> implements IQueryBuilder {

  private static final String SELECT = " p.idtipopersona, FALSE as ispostulado, p.idpersona as idpersona, td.tipodocumentoidentidad, p.numeroidentificacion, "
                                       + " p.primernombre, p.segundonombre, p.primerapellido, p.segundoapellido, p.alias, "
                                       + " (((((p.primerapellido || ' ') || COALESCE(p.segundoapellido, '')) || ', ') || p.primernombre) || ' ') || COALESCE(p.segundonombre, '') AS nombre, "
                                       + " p.iddocumentodeidentidad, "
  									   + " p.fechanacimiento ";
  private static final String FROM = " FROM persona p ";
  private static final String LEFT_JOIN_TIPO_DOCUMENTO = " LEFT JOIN tipodocumentoidentidad td ON p.iddocumentodeidentidad = td.idtipodocumentoidentidad ";

  private static final String WHERE_NOMBRE = " UPPER(nombre) like all ";
  private static final String WHERE_ID = " p.idpersona = :id ";
  private static final String WHERE_TIPO_DOCUMENTO = " p.iddocumentodeidentidad = :tipoDocumento ";
  private static final String WHERE_NUMERO_DOCUMENTO = " UPPER(p.numeroidentificacion) like  ";
  private static final String WHERE_ALIAS = " UPPER(p.alias) like all ";

  private static final String AND = " AND ";
  private static final String keyStart = "\"%";
  private static final String keyEnd = "%\"";
  private static final String arrayStart = "'{";
  private static final String arrayEnd = "}'";

  private PersonaConsultaDto dto;

  @Override
  public Query count() {
    Query query = null;
    if (super.getParamenters() != null && super.getParamenters().length > 0) {
      String queryString = createQuery(true);
      query = entityManager.createNativeQuery(queryString);
      setParametersToQuery(query);
    }
    return query;
  }

  @Override
  public Query select() {
    String queryString = createQuery(false);
    Query query = entityManager.createNativeQuery(queryString);
    setParametersToQuery(query);
    return query;
  }

  /**
   * Metodo encargado de crear el query para el COUNT, el SELECT
   * (teniendo en cuenta si se busca por nombre y otros campos). Para
   * poder usar el -WHERE nombre LIKE ALL ('{"%parte1%","%parte2%"}')-
   * es necesario crear un subquery;
   * 
   * @param withCount
   *          para concatenar el COUNT
   * @return query sin completo, sin parametros
   */
  private String createQuery(boolean withCount) {
    StringBuilder queryString = new StringBuilder("");

    if (withCount) {
      queryString.append("SELECT COUNT(idpersona) FROM ( ");
    }
    else if (isValidNameFilter()) {
      queryString.append("SELECT * FROM (");
    }

    String innerQueryString = createInnerQuery();
    queryString.append(innerQueryString);

    if (withCount) {
      queryString.append(" ) subqCount");
      queryString.append(" WHERE ").append(WHERE_NOMBRE);
      queryString.append(" ( " + extractSearchText(dto.getNombre()) + " ) ");
    }
    else if (isValidNameFilter()) {
      queryString.append(" ) subqSelectName ");
      queryString.append(" WHERE ").append(WHERE_NOMBRE);
      queryString.append(" ( " + extractSearchText(dto.getNombre()) + " ) ");
    }

    return queryString.toString();
  }

  /**
   * Metodo encargado de crear el INNER query en donde se consultan
   * todos los datos, se ordena y se filtra por todos los campos menos
   * nombre
   * 
   * @return el inner query
   */
  private String createInnerQuery() {
    StringBuilder innerQueryString = new StringBuilder("");
    boolean addAnd = false;

    innerQueryString.append("SELECT DISTINCT ");
    innerQueryString.append(SELECT);
    innerQueryString.append(FROM);
    innerQueryString.append(LEFT_JOIN_TIPO_DOCUMENTO);

    if (isValidFilters()) {
      innerQueryString.append(" WHERE ");

      if (dto.getId() != null) {
        if (addAnd)
          innerQueryString.append(AND);
        innerQueryString.append(WHERE_ID);
        addAnd = true;
      }

      if (dto.getIdTipoDocumento() != null) {
        if (addAnd)
          innerQueryString.append(AND);
        innerQueryString.append(WHERE_TIPO_DOCUMENTO);
        addAnd = true;
      }

      if (!StringUtils.isBlank(dto.getNumeroDocumento())) {
        if (addAnd)
          innerQueryString.append(AND);
        innerQueryString.append(WHERE_NUMERO_DOCUMENTO);
        innerQueryString.append( "'%"+dto.getNumeroDocumento()+"%'");
        addAnd = true;
      }

      if (!StringUtils.isBlank(dto.getAlias())) {
        if (addAnd)
          innerQueryString.append(AND);
        innerQueryString.append(WHERE_ALIAS);
        innerQueryString.append(" ( " + extractSearchText(dto.getAlias()) + " ) ");
        addAnd = true;
      }
    }

    String orders = createOrder();
    if (orders != null) {
      innerQueryString.append(" ORDER BY ");
      innerQueryString.append(orders);
    }

    return innerQueryString.toString();
  }

  /**
   * Metodo encargado de crear el orden de acuerdo a la columna
   * seleccionada
   * 
   * @return cadena con el ORDER BY
   */
  private String createOrder() {
    StringBuilder orders = new StringBuilder("");
    List<SortField> sortFields = arrangeableState.getSortFields();
    if (sortFields == null || sortFields.isEmpty())
      return null;

    FacesContext facesContext = FacesContext.getCurrentInstance();
    Iterator<SortField> iSortFields = sortFields.iterator();
    while (iSortFields.hasNext()) {
      SortField sortField = iSortFields.next();
      String expression = null;
      String columnName = (String) sortField.getSortBy().getValue(facesContext.getELContext());
      if ("id".equalsIgnoreCase(columnName)) {
        expression = "p.idpersona";
      }
      else if ("tipoDocumento".equalsIgnoreCase(columnName)) {
        expression = "td.tipodocumentoidentidad";
      }
      else if ("numeroDocumento".equalsIgnoreCase(columnName)) {
        expression = "p.numeroidentificacion";
      }
      else if ("primerNombre".equalsIgnoreCase(columnName)) {
        expression = "p.primernombre";
      }
      else if ("segundoNombre".equalsIgnoreCase(columnName)) {
        expression = "p.segundonombre";
      }
      else if ("primerApellido".equalsIgnoreCase(columnName)) {
        expression = "p.primerapellido";
      }
      else if ("segundoApellido".equalsIgnoreCase(columnName)) {
        expression = "p.segundoapellido";
      }
      else if ("alias".equalsIgnoreCase(columnName)) {
        expression = "p.alias";
      }

      if (SortOrder.ascending == sortField.getSortOrder())
        orders.append(expression + " ASC");
      else if (SortOrder.descending == sortField.getSortOrder())
        orders.append(expression + " DESC");

      if (iSortFields.hasNext())
        orders.append(",");
    }
    return orders.toString();
  }

  /**
   * Metodo encargado de settear los parametros de los filtros usados
   * en la consulta
   * 
   * @param query
   * @return query con los parametros setteados
   */
  private Query setParametersToQuery(Query query) {
    if (dto != null) {

      if (dto.getId() != null) {
        query.setParameter("id", dto.getId());
      }

      if (dto.getIdTipoDocumento() != null) {
        query.setParameter("tipoDocumento", dto.getIdTipoDocumento());
      }

//      if (!StringUtils.isBlank(dto.getNumeroDocumento())) {
//        query.setParameter("numeroDocumento", dto.getNumeroDocumento());
//      }
    }
    return query;
  }

  /**
   * Metodo encargado de partir la cadena (criterio de filtro: nombre)
   * para arreglarlo en un arreglo de datos con la forma
   * -('{"%parte1%","%parte2%"}')- usado por el LIKE ALL
   * 
   * @param searchText
   *          campo nombre en el popup (dto.nombre)
   * @return arreglo de datos usado por el LIKE ALL
   */
  private String extractSearchText(String searchText) {
    StringBuilder arrayData = new StringBuilder();
    StringTokenizer stringTokenizer = new StringTokenizer(searchText.toUpperCase());
    String colon = "";

    arrayData.append(arrayStart);
    while (stringTokenizer.hasMoreElements()) {
      arrayData.append(colon);
      colon = ",";

      arrayData.append(keyStart);
      arrayData.append(returnStringNoSpecialChar(stringTokenizer.nextElement()));
      arrayData.append(keyEnd);
    }
    arrayData.append(arrayEnd);

    return arrayData.toString();
  }

  /**
   * Metodo encargado de retornar el String sin caracteres especiales,
   * reemplazandolos por el '_' (cualquier caracter simple)
   * 
   * @param filterValue
   * @return cadena sin caracteres especiales
   */
  private String returnStringNoSpecialChar(Object filterValue) {
    char[] characters = ((String) filterValue).toCharArray();
    StringBuilder newFilterValue = new StringBuilder();
    for (int h = 0; h < characters.length; h++) {
      if (isSpecial(characters[h])) {
        newFilterValue.append("_");
      }
      else {
        newFilterValue.append(characters[h]);
      }
    }
    return newFilterValue.toString();

  }

  /**
   * Valida si el char es un caracter especial
   * 
   * @param a
   * @return true si tiene algun caracter
   */
  private boolean isSpecial(char a) {
    return (a == SijypConstants.ENIE_MINUSCULA || a == SijypConstants.A_TILDE_MINUSCULA
            || a == SijypConstants.E_TILDE_MINUSCULA || a == SijypConstants.I_TILDE_MINUSCULA
            || a == SijypConstants.O_TILDE_MINUSCULA || a == SijypConstants.U_TILDE_MINUSCULA || a == SijypConstants.U_DIARESIS_MINUSCULA);
  }

  /**
   * Valida si el filtro nombre es valido
   * 
   * @return
   */
  private boolean isValidNameFilter() {
    return dto != null && !StringUtils.isBlank(dto.getNombre());
  }

  /**
   * Valida si los filtros son validos
   * 
   * @return
   */
  private boolean isValidFilters() {
    if (super.getParamenters() != null && super.getParamenters().length > 0) {
      dto = (PersonaConsultaDto) this.parameters[0];

      if (dto != null) {
        if ((dto.getId() != null) || (dto.getIdTipoDocumento() != null)
            || (!StringUtils.isBlank(dto.getNumeroDocumento()))
            || (!StringUtils.isBlank(dto.getAlias()))) {
          return true;
        }
      }
    }
    return false;
  }
}